import type { QueueStatus } from '../../lib/queue_handler'
import type { StyleElementType } from '../../lib/style_injector'
import type {
  ElementCSSIdOption,
  ElementCSSStyleIdOption,
  ElementDealOptions,
  IElementCSSOption,
} from '../../types/constants'
import { queryHTMLElement } from '../../lib/element_query'
import { EventEmitter } from '../../lib/event_emitter'
import { PollingHandler } from '../../lib/polling_handler'
import { QueueHandler } from '../../lib/queue_handler'
import { InjectorType, StyleInjectorManager } from '../../lib/style_injector'
import { generateElementId } from '../../lib/utils/base/crypto'
import { ToolKit } from '../../lib/utils/toolkit'
import {
  ComponentStyleId,
  DEFAULT_ELEMENT_CONFIG_VALUES,
  ELEMENT_SAVE_CONFIG,
  ElementConfigSource,
  ElementSelectorDOMScopeType,
  ElementSelectorType,
  EVENT_EMITTER_NAMES,
  HIDE_CLASS_STYLE,
  logger,
} from '../../types/constants'
import { SYMBOLS } from '../../types/interfaces'
import { WebElement } from '../config/web_element'
import { ElementHideCssOperation } from './types'

export class ElementCSSHandler {
  // @Decorators.Identity
  readonly id: string = 'element_css_handler'
  readonly name: string = 'ElementCSSHandler'
  // @Decorators.PublicState
  private static instance: ElementCSSHandler
  // @Decorators.Dependency
  private eventEmitter: EventEmitter = EventEmitter.getInstance()
  private webElement: WebElement = WebElement.getInstance()
  private pollingHandler: PollingHandler = PollingHandler.getInstance()
  private styleInjectorManager: StyleInjectorManager = StyleInjectorManager.getInstance()
  // @Decorators.Core
  private cssStyleId: string = ComponentStyleId.ElementCSSHandler
  private defaultInjectorType: InjectorType = InjectorType.Standard
  // @Decorators.TemporaryState
  private styleStates: Map<string, { type: InjectorType, element: HTMLStyleElement | null }> = new Map()
  // 配置中的 styleId 与 this.styleStates 中的键相关联
  private hiddenRules: Map<string, {
    name: string
    domScope: ElementSelectorDOMScopeType
    hostSelector: string
    selector: string
    source: string
    styleId: string
  }> = new Map()

  // 队列处理，确保串行执行
  private queue: QueueHandler = new QueueHandler(1)
  private pendingBatch: IElementCSSOption[] = []
  private batchTimer: number | null = null

  /**
   * 获取单例实例
   */
  public static getInstance(): ElementCSSHandler {
    if (!ElementCSSHandler.instance) {
      ElementCSSHandler.instance = new ElementCSSHandler()
    }
    return ElementCSSHandler.instance
  }

  /**
   * 生成样式元素
   */
  async generateStyleTag(options: IElementCSSOption, id: string): Promise<HTMLStyleElement | null> {
    const { injectorType = this.defaultInjectorType, hostSelector = '', selector } = options

    let targetElement: StyleElementType = null

    switch (injectorType) {
      case InjectorType.Shadow: {
        targetElement = queryHTMLElement(hostSelector)?.shadowRoot
        break
      }
      case InjectorType.Iframe:
        targetElement = queryHTMLElement(hostSelector)
        break
    }

    const isShadowUnavailable = this.isShadowUnavailable(injectorType, targetElement)

    const handler = () => {
      // 处理 Shadow DOM
      if (isShadowUnavailable)
        targetElement = queryHTMLElement(hostSelector)?.shadowRoot

      if (injectorType === InjectorType.Shadow) {
        // 隐藏元素
        ToolKit.ui.element.hideElement(
          selector,
          { hostSelector, domScope: ElementSelectorDOMScopeType.ShadowRoot },
        )
      }

      return this.styleInjectorManager.createStyleTagWithType(
        injectorType,
        id,
        '',
        {
          allowEmpty: true,
          allowUpdate: false,
          targetElement,
        },
      )!
    }

    let styleElement: HTMLStyleElement | null

    if (isShadowUnavailable) {
      styleElement = await this.pollingHandler.api.awaitElementWithResult(
        hostSelector,
        handler,
        { shadowDOM: {
          enabled: true,
          checkChildrenExist: true,
          childrenSelector: selector,
        } },
      )
    }
    else {
      styleElement = handler()
    }

    return styleElement
  }

  /**
   * 检查 Shadow DOM 是否不可用
   */
  isShadowUnavailable(injectorType: InjectorType, targetElement: StyleElementType): boolean {
    return injectorType === InjectorType.Shadow && !targetElement
  }

  /**
   * 应用所有隐藏样式
   */
  private async applyAllHideStyles(): Promise<void> {
    try {
      // 收集所有需要更新的样式 ID（去重）
      const styleIds = new Set<string>()
      this.hiddenRules.forEach(({ styleId }) => {
        styleIds.add(styleId)
      })

      // 并行更新所有样式
      const updatePromises = Array.from(styleIds).map(styleId =>
        this.applyHideStyle(styleId, { newStyleTag: false }),
      )

      await Promise.all(updatePromises)
      this.eventEmitter.emit(EVENT_EMITTER_NAMES.refreshAdEngine)

      logger.debug(`[${this.name}] ${SYMBOLS.STATUS.REFRESH} 批量更新 ${styleIds.size} 个样式元素完成`)
    }
    catch (error) {
      logger.error(`[${this.name}] 批量更新样式失败`, error)
    }
  }

  /**
   * 应用隐藏样式到指定样式元素
   */
  private async applyHideStyle(styleId: string, props: { newStyleTag: boolean, onlyCreate?: boolean }, options?: IElementCSSOption): Promise<void> {
    const { newStyleTag, onlyCreate = false } = props

    if (!styleId) {
      logger.warn(`[${this.name}] 样式元素 ID 为空，跳过处理`)
      return
    }

    try {
      let styleState = this.styleStates.get(styleId)

      // 【注意】为避免频繁创建样式元素，所以这里没有处理 element 为 null 的情况
      // 如果样式状态不存在且提供了选项，创建新的样式元素
      if (!styleState) {
        if (!options) {
          if (newStyleTag) {
            logger.warn(`[${this.name}] 样式元素 id='${styleId}' 未配置选项，跳过处理`)
          }
          return
        }

        styleState = {
          type: options.injectorType || this.defaultInjectorType,
          element: await this.generateStyleTag(options, styleId),
        }
        this.styleStates.set(styleId, styleState)

        if (onlyCreate)
          return
      }

      if (!styleState.element) {
        logger.debug(`[${this.name}] 样式元素 id='${styleId}' 不存在，跳过处理`)
        return
      }

      const rules = Array.from(this.hiddenRules)
        .filter(([_, { styleId: hiddenStyleId }]) => hiddenStyleId === styleId)

      if (rules.length === 0) {
        logger.debug(`[${this.name}] 样式元素 id='${styleId}' 不存在隐藏选择器，清空内容`)
        styleState.element.textContent = ''
        return
      }

      // 生成 CSS 内容
      const totalRules = rules.length
      const timestamp = new Date().toLocaleString()

      styleState.element.textContent = this.generateHideCSS(rules, totalRules, timestamp)
    }
    catch (error) {
      logger.error(`[${this.name}] 应用隐藏样式失败: id='${styleId}'`, error)
    }
  }

  /**
   * 生成隐藏 CSS 内容
   */
  private generateHideCSS(
    rules: [string, { name: string, selector: string }][],
    totalRules: number,
    timestamp: string,
  ): string {
    const cssRules = rules.map(([id, { name, selector }], index) =>
      `/* 【${index + 1}】【${id}】${name} */\n${selector}`,
    ).join(',\n')

    return ToolKit.string.removeEmptyLines(`
    /* ===================== 基本信息 ===================== */
    /* 规则总数: ${totalRules} */
    /* 更新时间: ${timestamp} */
    /* ===================== 隐藏类 ===================== */
    ${HIDE_CLASS_STYLE}
    
    /* ===================== 隐藏规则 ===================== */
    ${cssRules} {
      display: none !important;
    }
  `)
  }

  /**
   * 应用元素样式规则
   */
  async applyRule(
    options: IElementCSSOption,
    immediate: boolean = true,
  ): Promise<void> {
    try {
      const { name, domScope, selectorType, hostSelector = '', selector, source } = options
      const isJQuerySelector = selectorType === ElementSelectorType.JQuery

      if (isJQuerySelector) {
        logger.debug(`[${this.name}] 不支持 jQuery 选择器: ${name}`)
        return
      }

      if (!isJQuerySelector && ToolKit.ui.element.detectJQuerySelector(selector)) {
        logger.warn(`[${this.name}] 元素选择器'${name}'类型错配置成 ${selectorType}，实际为 ${ElementSelectorType.JQuery}`, selector)
        return
      }

      const selectorId = await this.generateId(options)

      if (this.hiddenRules.has(selectorId)) {
        logger.warn(`[${this.name}] 选择器已存在: ${selectorId}`, this.hiddenRules.get(selectorId))
        return
      }

      const styleId = await this.generateStyleTagId(options)
      this.hiddenRules.set(selectorId, { name, domScope, hostSelector, selector, source, styleId })

      logger.debug(`[${this.name}] 添加选择器: ${name} -> ${styleId}`)

      await this.applyHideStyle(styleId, { newStyleTag: true, onlyCreate: !immediate }, options)
    }
    catch (error) {
      logger.error(`[${this.name}] 添加选择器失败: ${options.name}`, error)
    }
  }

  /**
   * 移除元素样式规则
   */
  async removeRule(options: IElementCSSOption): Promise<void> {
    // 生成选择器 ID
    const selectorId = await this.generateId(options)

    if (!this.hiddenRules.has(selectorId))
      return

    // 获取样式 ID
    const styleId = this.hiddenRules.get(selectorId)?.styleId

    if (!styleId)
      return

    // 删除选择器
    this.hiddenRules.delete(selectorId)

    // 检查并清理未使用的样式
    await this.cleanupUnusedStyle(styleId)

    logger.debug(`[${this.name}] 移除'${options.name}'选择器：${options.selector}`)
  }

  /**
   * 批量添加元素样式规则
   */
  addRules(rules: IElementCSSOption[]): void {
    this.handleRules(rules, ElementHideCssOperation.Inject)
  }

  /**
   * 批量删除元素样式规则
   */
  deleteRules(rules: IElementCSSOption[]): void {
    this.handleRules(rules, ElementHideCssOperation.Remove)
  }

  /**
   * 批量处理元素样式规则
   */
  handleRules(rules: IElementCSSOption[], type: ElementHideCssOperation): void {
    if (rules.length === 0)
      return

    if (![ElementHideCssOperation.Inject, ElementHideCssOperation.Remove].includes(type)) {
      logger.error(`[${this.name}] 未知操作类型: ${type}`)
      return
    }

    // 处理现有规则，避免重复规则
    const newRules = rules.filter(rule =>
      type === ElementHideCssOperation.Inject
        ? !this.isElementHidden(rule)
        : this.isElementHidden(rule))

    if (newRules.length === 0) {
      if (type === ElementHideCssOperation.Inject) {
        logger.debug(`[${this.name}] 所有规则已存在，跳过添加`)
      }
      else {
        logger.debug(`[${this.name}] 所有规则均不存在，跳过删除`)
      }

      return
    }

    // 同步添加到待处理批次
    this.pendingBatch.push(...newRules)

    if (type === ElementHideCssOperation.Inject) {
      logger.debug(`[${this.name}] 添加 ${newRules.length} 个新规则，跳过 ${rules.length - newRules.length} 个已存在规则`)
    }
    else {
      logger.debug(`[${this.name}] 删除 ${newRules.length} 个旧规则，跳过 ${rules.length - newRules.length} 个未存在规则`)
    }

    this.scheduleBatchProcessing()
  }

  /**
   * 调度批次处理
   */
  private scheduleBatchProcessing(): void {
    this.stopBatchTimer()

    this.batchTimer = window.setTimeout(() => {
      this.processCurrentBatch().catch((error) => {
        logger.error(`[${this.name}] 处理批次异常`, error)
      })
    }, 50)
  }

  /**
   * 处理当前批量添加选择器
   */
  async processCurrentBatch(): Promise<void> {
    if (this.pendingBatch.length === 0)
      return

    // 获取当前批次并清空
    const batch = [...this.pendingBatch]
    const type = batch.some(rule => rule.operation === ElementHideCssOperation.Inject)
      ? ElementHideCssOperation.Inject
      : ElementHideCssOperation.Remove

    this.pendingBatch = []
    this.stopBatchTimer()

    try {
      // 使用队列确保串行执行
      await this.queue.add(() => this.processBatch(batch, type))
    }
    catch (error) {
      logger.error(`[${this.name}] 处理当前批次失败`, {
        error,
        batchSize: batch.length,
      })

      // 失败时重新加入批次（可选）
      // this.pendingBatch.unshift(...batch)
    }
  }

  /**
   * 处理批量添加元素样式规则
   */
  async processBatch(rules: IElementCSSOption[], type: ElementHideCssOperation): Promise<void> {
    if (rules.length === 0)
      return

    logger.debug(`[${this.name}] 开始处理批次，数量: ${rules.length}`)

    try {
      let results: PromiseSettledResult<void>[]

      // 批量添加选择器到映射表
      if (type === ElementHideCssOperation.Inject) {
        results = await Promise.allSettled(
          rules.map(rule => this.applyRule(rule, false)),
        )
      }
      else {
        results = await Promise.allSettled(
          rules.map(rule => this.removeRule(rule)),
        )
      }

      // 统计结果
      const successCount = results.filter(r => r.status === 'fulfilled').length
      const failedCount = results.filter(r => r.status === 'rejected').length

      // 记录失败详情
      if (failedCount > 0) {
        results.forEach((result, index) => {
          if (result.status === 'rejected') {
            logger.warn(`[${this.name}] ${type === ElementHideCssOperation.Inject ? '添加' : '移除'}规则失败: ${rules[index].name}`, result.reason)
          }
        })
      }

      // 如果有成功添加的选择器，更新样式
      if (successCount > 0) {
        await this.applyAllHideStyles()
        logger.debug(`[${this.name}] ${SYMBOLS.UI.BULLSEYE} 批次处理完成`, {
          success: successCount,
          failed: failedCount,
          total: rules.length,
          successRate: `${((successCount / rules.length) * 100).toFixed(1)}%`,
        })
      }
      else {
        logger.warn(`[${this.name}] 批次处理无成功项`, {
          total: rules.length,
          failed: failedCount,
        })
      }
    }
    catch (error) {
      logger.error(`[${this.name}] 批次处理异常`, {
        error,
        batchSize: rules.length,
      })
      throw error // 重新抛出以便队列可以处理错误
    }
  }

  /**
   * 获取样式元素
   */
  getStyleTagElement(id: string): HTMLStyleElement | null {
    return this.styleStates.get(id)?.element || null
  }

  /**
   * 判断元素是否被隐藏
   */
  shouldElementHidden(options: ElementCSSIdOption): boolean {
    return this.isElementHidden(options) && ToolKit.ui.element.isHideStyleApplied(options)
  }

  /**
   * 检查元素是否在隐藏列表中
   */
  private isElementHidden(options: ElementCSSIdOption): boolean {
    const { domScope, hostSelector = '', selector } = options

    return Array.from(this.hiddenRules.values()).some(config =>
      config.domScope === domScope
      && config.hostSelector === hostSelector
      && config.selector === selector,
    )
  }

  /**
   * 生成唯一的选择器 ID
   */
  async generateId(options: ElementCSSIdOption): Promise<string> {
    const { domScope, hostSelector = '', selector } = options
    const content = ToolKit.string.contactList([
      domScope,
      hostSelector,
      selector,
    ])
    return await this.calcId(content)
  }

  /**
   * 生成样式元素 ID
   */
  async generateStyleTagId(options: ElementCSSStyleIdOption): Promise<string> {
    const { domScope, hostSelector = '' } = options

    if (domScope === ElementSelectorDOMScopeType.Document)
      return this.cssStyleId

    const content = ToolKit.string.contactList([
      domScope,
      hostSelector,
    ])
    return await this.calcId(content)
  }

  /**
   * 计算 ID
   */
  async calcId(content: string): Promise<string> {
    return await generateElementId(content, { useCache: true })
  }

  /**
   * 停止批量处理定时器
   */
  private stopBatchTimer(): void {
    if (this.batchTimer) {
      clearTimeout(this.batchTimer)
      this.batchTimer = null
    }
  }

  /**
   * 清空所有隐藏规则
   */
  clearAll(): void {
    this.styleStates.forEach(({ type, element }, id) => {
      if (!element)
        return

      this.styleInjectorManager.removeStyleTag(id, { type })
    })

    this.styleStates.clear()
    this.hiddenRules.clear()

    logger.info(`[${this.name}] 已清空所有 CSS 隐藏规则`)
  }

  /**
   * 清空所有自定义隐藏规则
   */
  async clearCustomSelectors(): Promise<void> {
    let count = 0

    // 直接遍历，避免创建中间数组
    for (const [id, config] of this.hiddenRules.entries()) {
      if (config.source === ElementConfigSource.Custom) {
        this.hiddenRules.delete(id)
        count++
      }
    }

    if (count === 0)
      return

    await this.applyAllHideStyles()
    logger.hint(`[${this.name}] 已清空所有自定义隐藏规则，共 ${count} 条`)
  }

  /**
   * 清理未使用的样式元素
   */
  private async cleanupUnusedStyle(styleId: string): Promise<void> {
    // 检查该样式是否还有其他选择器在使用
    const isStyleInUse = Array.from(this.hiddenRules.values()).some(
      config => config.styleId === styleId,
    )

    if (!isStyleInUse) {
      // 没有其他选择器使用该样式，清理样式元素
      const styleState = this.styleStates.get(styleId)

      if (!styleState)
        return

      const { type, element } = styleState

      if (!element)
        return

      this.styleInjectorManager.removeStyleTag(styleId, { type })
      this.styleStates.delete(styleId)

      logger.debug(`[${this.name}] ${SYMBOLS.UI.TRASH} 清理未使用的样式元素: ${styleId}`)
    }
    else {
      // 还有选择器在使用，只更新内容
      await this.applyHideStyle(styleId, { newStyleTag: false })
    }
  }

  /**
   * 等待所有任务完成
   */
  async waitForIdle(): Promise<void> {
    // 先处理当前批次
    if (this.pendingBatch.length > 0)
      await this.processCurrentBatch()

    // 等待队列空闲
    await this.queue.onIdle()

    logger.debug(`[${this.name}] 所有任务已完成`)
  }

  /**
   * 获取队列状态
   */
  getQueueStatus(): {
    pendingBatch: number
    queueStatus: QueueStatus
    hiddenRulesCount: number
    styleStatesCount: number
  } {
    return {
      pendingBatch: this.pendingBatch.length,
      queueStatus: this.queue.status,
      hiddenRulesCount: this.hiddenRules.size,
      styleStatesCount: this.styleStates.size,
    }
  }

  /**
   * 获取所有规则统计信息
   */
  getRulesStatistics(): {
    total: number
    bySource: Record<string, number>
    byDomScope: Record<string, number>
    pendingBatch: number
  } {
    const stats = {
      total: this.hiddenRules.size,
      bySource: {} as Record<string, number>,
      byDomScope: {} as Record<string, number>,
      pendingBatch: this.pendingBatch.length,
    }

    // 统计按源类型分组
    this.hiddenRules.forEach((config) => {
      stats.bySource[config.source] = (stats.bySource[config.source] || 0) + 1
      stats.byDomScope[config.domScope] = (stats.byDomScope[config.domScope] || 0) + 1
    })

    return stats
  }

  /**
   * 销毁管理器
   */
  destroy(): void {
    this.clearAll()
    this.stopBatchTimer()
    logger.info(`[${this.name}] 已销毁 CSS 注入管理器`)
  }

  /**
   * 批量处理元素配置项
   */
  processElementVisibilitySettings(
    settings: Array<{ key: string, hide: boolean, source: ElementConfigSource }>,
  ): ElementDealOptions[] {
    return settings.map(item => this.processElementVisibilitySetting(item))
  }

  /**
   * 处理单个元素配置项并返回处理选项
   */
  processElementVisibilitySetting(item: {
    key: string
    hide: boolean
    source: ElementConfigSource
  }): ElementDealOptions {
    const { key, hide, source } = item
    const { elementType, enName, selector } = ToolKit.ui.element.getElementConfig(key)
    const config = elementType === ELEMENT_SAVE_CONFIG.ELEMENT.id ? this.webElement.getConfig(enName, selector) : undefined
    const enable = config?.enable ?? DEFAULT_ELEMENT_CONFIG_VALUES.enable!

    return {
      enable,
      hide: Boolean(hide),
      domScope: config?.domScope ?? DEFAULT_ELEMENT_CONFIG_VALUES.domScope!,
      selectorType: config?.selectorType ?? DEFAULT_ELEMENT_CONFIG_VALUES.selectorType!,
      hostSelector: config?.hostSelector ?? DEFAULT_ELEMENT_CONFIG_VALUES.hostSelector!,
      selector,
      name: config?.nameZh ?? '',
      type: elementType,
      clearChildStyles: config?.clearChildStyles ?? DEFAULT_ELEMENT_CONFIG_VALUES.clearChildStyles!,
      compoundOptions: config?.compoundOptions,
      listen: config?.listen ?? DEFAULT_ELEMENT_CONFIG_VALUES.listen!,
      listenOptions: config?.listenOptions,
      useCssInjection: config?.useCssInjection ?? DEFAULT_ELEMENT_CONFIG_VALUES.useCssInjection!,
      handler: config?.handler,
      source,
    }
  }
}
